local json = require "cjson"
local config = require "config"

---! 映射表
local load_map = {}     ---! 正在加载的玩家对象
local user_map = {}     ---! 已经加载的玩家对象
local temp_map = {}     ---! 临时缓存的玩家对象

---! 事件表
local enter_world_map = {}  ---! 进入游戏时的处理事件
local leave_world_map = {}  ---! 离开游戏时的处理事件

local function _load_user(userId)
    ---! 临时对象
    local userOb = temp_map[userId]
    if userOb then
        return userOb
    end

    ---! 创建对象
    local userOb = USER_OB:create()
    userOb:set_temp("userId", userId)

    ---! 加载数据
    local ok = userOb:load()
    if not ok then
        return
    end

    ---! 记录对象
    temp_map[userId] = userOb

    ---! 返回对象
    return userOb
end

local function _check_online_user(user_id, user_ob, check_time)
    local save_time = user_ob:query_temp("save_time")
    if not save_time then
        ---! 记录保存时间
        user_ob:set_temp("save_time", check_time)
        return
    end

    local cmd_time = user_ob:query_temp("command_time")
    if not cmd_time or check_time - cmd_time > 300 then
        ---! 断开玩家连接
        USER_D:disconnect(user_ob, 3)

        ---! 安排玩家下线
        USER_D:leave_world(user_ob)
        return
    end

    if check_time - save_time > 180 then
        ---! 保存玩家数据
        user_ob:save()
    end
end

local function _check_offline_user(user_id, user_ob, check_time)
    if USER_D:is_loading(user_id) then
        return
    end

    if USER_D:find_user(user_id) then
        return
    end

    local cached_time = user_ob:query_temp("cached_time")
    if cached_time and check_time < cached_time + 600 then
        return
    end

    temp_map[user_id] = nil
end

---! 定时保存用户数据
local function _check_user()
    ---! 获取当前时间
    local now_time = os.time()

    ---! 遍历在线玩家
    for user_id, user_ob in pairs(user_map) do
        local ok = pcall(_check_online_user, user_id, user_ob, now_time)
        if not ok then
            spdlog.error("user", string.format("save user data failed, user = %s, data = %s", user_ob:get_id(), json.encode(user_ob:query_entire_dbase())))
        end
    end

    ---! 遍历缓存玩家
    for user_id, user_ob in pairs(temp_map) do
        local ok = pcall(_check_offline_user, user_id, user_ob, now_time)
        if not ok then
            spdlog.error("user", string.format("clean cached data failed, user = %s, data = %s", user_ob:get_id(), json.encode(user_ob:query_entire_dbase())))
        end
    end
end
-------------------------------------------------------------------------------
---! 对外接口
-------------------------------------------------------------------------------
USER_D = {}

function USER_D:find_user(userId)
    local userOb = user_map[userId]
    if userOb then
        return userOb
    end
end

---! 查找临时对象
function USER_D:find_temp_user(userId)
    local userOb = temp_map[userId]
    if userOb then
        return userOb
    end
end

---! 判断是否正在加载
function USER_D:is_loading(userId)
    if load_map[userId] then
        return true
    end
    return false
end

---! 加载对象
function USER_D:load_user(userId)
    ---! 获取当前时间
    local now = os.clock()
    ---! 设置加载状态
    load_map[userId] = now
    ---! 开始加载对象
    spdlog.debug("userd", string.format("user [%s] start load ...", userId))
    local userOb = _load_user(userId)
    spdlog.debug("userd", string.format("user [%s] load cost time = %s", userId, os.clock() - now))
    ---! 解除加载状态
    load_map[userId] = nil
    return userOb
end

---! 初始对象
function USER_D:init_user(userOb)
    if userOb:get_create_time() then
        return
    end

    spdlog.info("user", string.format("init user [%s] data", userOb:get_id()))

    ---! 设置角色创建时间
    userOb:set_create_time()
    ---! 设置初始伙伴等级
    userOb:init_partner()
    ---! 设置初始装备等级
    userOb:init_equip()
    ---! 设置初始道具
    userOb:init_item()
    ---! 设置初始技能
    userOb:init_skill()
    ---! 设置初始翅膀
    userOb:init_wing()
    ---! 设置初始神器
    userOb:init_artifact()
    ---! 保存玩家数据
    userOb:save()

    ---! 记录创建日志
    LOG_D:write_login_log("init_user", 1, userOb)
end

---! 进入游戏
function USER_D:enter_world(user_ob)
    local user_id = user_ob:query_temp("userId")
    user_map[user_id], temp_map[user_id] = user_ob, nil
    spdlog.info("userd", string.format("user [%s] enter world succ.", user_id))

    ---! 触发进入游戏相关事件
    for _, func in pairs(enter_world_map) do
        xpcall(func, error_traceback, user_ob)
    end

    ---! 记录当前玩家进入时间
    user_ob:set_temp("start_online_time", os.time())
end

---! 离开游戏
function USER_D:leave_world(userOb)
    local clientAddr = userOb:query_temp("clientAddr")
    local clientId = userOb:query_temp("clientId")
    if clientAddr and clientId then
        return
    end

    ---! 清理临时数据
    userOb:delete_temp("save_time")
    userOb:delete_temp("command_time")

    ---! 触发相关事件
    for _, func in pairs(leave_world_map) do
        xpcall(func, error_traceback, userOb)
    end

    ---! 优先保存一下
    userOb:save()

    ---! 缓存当前对象
    local userId = userOb:query_temp("userId")
    user_map[userId], temp_map[userId] = nil, userOb
    spdlog.info("userd", string.format("user [%s] leave world succ.", userId))

    ---! 记录缓存时间
    userOb:set_temp("cached_time", os.time())

    ---! 记录在线时长
    local online_time = userOb:set_temp("last_online_time", userOb:get_online_time())

    ---! 记录登出日志
    LOG_D:write_login_log("logout", 1, userOb, string.format("%d", online_time))
end

---! 重连游戏
function USER_D:reconnect(userOb)
    local userId = userOb:query_temp("userId")
    spdlog.info("userd", string.format("user [%s] reconnect world ...", userId))

    local func = userOb:query_temp("reconnect_callback")
    if func then
        func(userOb)
    end
end

---! 断开游戏
function USER_D:disconnect(userOb, reason)
    local clientAddr = userOb:query_temp("clientAddr")
    if not clientAddr then
        return
    end

    local clientId = userOb:query_temp("clientId")
    if not clientId then
        return
    end

    spdlog.info("userd", string.format("user [%s] disconnect world ...", userOb:get_id()))
    local func = userOb:query_temp("disconnect_callback")
    if func then
        func(userOb)
    end

    ---! 通知玩家离开游戏
    local result = {}
    result.reason = reason or 0
    USER_D:send_packet(clientAddr, clientId, "MSGS2CLeaveGame", result)
    -- USER_D:send_packet2(clientAddr, clientId, "MSG_DISCONNECT", { MsgType = "MSGS2CLeaveGame", MsgValue = result, })

    ---! 断开客户端连接
    LOGIN_D:disconnect(userOb)

    ---! 记录重登日志
    LOG_D:write_login_log("disconnect", 1, userOb, string.format("%d", userOb:get_online_time()), string.format("%d", reason or 0))
end

---! 获取所有在线玩家
function USER_D:get_all_users()
    return user_map
end

---! 获取所有临时玩家
function USER_D:get_all_temp_users()
    return temp_map
end

---! 获取所有加载玩家
function USER_D:get_all_load_users()
    return load_map
end

---! 发送大厅消息
function USER_D:send_hall_info(userOb)
    local result = {}
    result.playerInfo = userOb:generate_player_info()
    result.serverTime = os.time()
    result.enableDebug = false

    result.luck = userOb:query("critup_luck") or 0 --!幸运值
    result.stage = userOb:get_stage_level() --!当前所在关卡
    result.open_number = userOb:get_open_number() --!金宝箱打开次数
    result.open_number_silver = userOb:get_open_number_silver() --!普通宝箱打开次数
    result.skill = userOb:get_use_skill()
    result.wing = userOb:get_use_wing()
    result.artifact = userOb:get_use_artifact()
    result.gain_number = userOb:query("box_number", 10002) or 0
    result.gain_number_silver = userOb:query("box_number", 10003) or 0
    result.level = userOb:get_level()
    userOb:send_packet("MSGS2CGetHallInfo", result)
end

---! 发包
function USER_D:send_packet(client_addr, client_id, msg_type, msg_value)
    local root = {}
    root.UserName = client_id
    root.SendTime = os.time()
    root.Sender = config.server.game_channel
    root.MsgData = { MsgType = msg_type, MsgValue = msg_value, }

    local packet = json.encode(root)
    SERVICE_D:post("client_channel", client_addr, packet)
end

---! 发包
function USER_D:send_packet2(client_addr, client_id, msg_type, msg_data)
    local root = {}
    root.UserName = client_id
    root.SendTime = os.time()
    root.Sender = config.server.game_channel
    root.MsgType = msg_type
    root.MsgData = msg_data

    local packet = json.encode(root)
    SERVICE_D:post("client_channel", client_addr, packet)
end

---! 注册进入游戏时的事件处理
function USER_D:register_enter_world(event_name, event_func)
    enter_world_map[event_name] = event_func
end

---! 注册离开游戏时的事件处理
function USER_D:register_leave_world(event_name, event_func)
    leave_world_map[event_name] = event_func
end

-------------------------------------------------------------------------------
---! 启动接口
-------------------------------------------------------------------------------
register_post_init(function()
    TIMER_D:start_timer(30, _check_user)

    ---! 仅为于便于观察服务器在线人数相关状态
    TIMER_D:start_timer(300, function()
        spdlog.info("user", string.format("user_map size : %d, temp_map size : %d, load_map size : %d, used_mem size : %s",
                                           table.size(user_map), table.size(temp_map), table.size(load_map), collectgarbage("count")))

        ---! 在线日志
        LOG_D:write_online_log(table.size(user_map), table.size(temp_map), table.size(load_map))
    end)
end)

register_post_dest(function()
    ---! 遍历在线玩家
    for _, user_ob in pairs(user_map) do
        local ok = pcall(function()
            ---! 断开玩家连接
            USER_D:disconnect(user_ob, 4)

            ---! 安排玩家下线
            USER_D:leave_world(user_ob)
        end)

        if not ok then
            -- spdlog.error("user", string.format("save user data failed, user = %s, data = %s", user_ob:get_id(), serialize(user_ob:query_entire_dbase())))
        end
    end
end)
